package cn.itcast.nems.common.captcha;

import java.awt.Color;
import java.awt.Font;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.List;

import javax.imageio.ImageIO;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;

import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import cn.itcast.nems.common.captcha.bean.CaptchaBean;
import lombok.extern.slf4j.Slf4j;
import nl.captcha.Captcha;
import nl.captcha.noise.CurvedLineNoiseProducer;
import nl.captcha.text.renderer.ColoredEdgesWordRenderer;

@Slf4j
@Component
//@RefreshScope
public class CaptchaServiceImpl implements CaptchaService {

	@Value("${JWT_SECRET_KEY:96b306f440252a160bca8cf2422405cb}")
	private String jwtSecretKey;
	
	private static final String IMAGE_BASE64_PREFIX = "data:image/gif;base64,";

	@Override
	public boolean verify(String captcha, String token) {
	    byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue(), jwtSecretKey.getBytes()).getEncoded();
        SymmetricCrypto des = new SymmetricCrypto(SymmetricAlgorithm.DES, key);
        token = des.decryptStr(token);
        
		DecodedJWT jwtDecoded = null;
		try {
			jwtDecoded = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build().verify(token);
		} catch (JWTVerificationException e) {
			log.info("验证码TOKEN错误！" + e.getMessage());
			return false;
		}
		
		// 验证码是否正确
		String audience = jwtDecoded.getAudience().get(0);
		return audience.equalsIgnoreCase(generateJWTAudience(jwtDecoded.getExpiresAt(), captcha));
	}

	@Override
	public CaptchaBean generateCaptcha() {
		Captcha captcha = buildCaptcha();
		return new CaptchaBean(generateToken(captcha.getAnswer()), imgToBase64(captcha.getImage()));
	}

	/**
	 * 图片转base64
	 *
	 * @param img 图片
	 * @return base64
	 */
	private String imgToBase64(BufferedImage img) {
		final ByteArrayOutputStream os = new ByteArrayOutputStream();
		try {
			ImageIO.write(img, "png", os);
			return IMAGE_BASE64_PREFIX + Base64.getEncoder().encodeToString(os.toByteArray());
		} catch (final IOException ioe) {
			log.error("验证码图片转BASE64错误！", ioe);
		}
		return null;
	}

	/**
	 * 构建验证码对象
	 * 
	 * @return 验证码对象
	 */
	private Captcha buildCaptcha() {
		List<Font> fontList = new ArrayList<>();
		fontList.add(new Font("Arial", Font.ITALIC, 20));// 可以设置斜体之类的
		fontList.add(new Font("Courier", Font.BOLD, 20));
		fontList.add(new Font(null, Font.CENTER_BASELINE, 20));

		// 加入多种颜色后会随机显示 字体空心
		List<Color> colorList = new ArrayList<>();
		colorList.add(Color.blue);

		return new Captcha.Builder(70, 35)
				.addText(new ColoredEdgesWordRenderer(colorList, fontList, 1))
				.addNoise(new CurvedLineNoiseProducer(Color.blue, 2)).build();
	}

	/**
	 * 生成验证码token <p/>
	 * 生成算法：AES(JWT)
	 * 
	 * @param answer 验证码答案
	 * @return 验证码对应的TOKEN
	 */
	private String generateToken(String answer) {
		final int expSecond = 180; //三分钟
		long currentTimeMillis = System.currentTimeMillis();
		Date currentAt = new Date(currentTimeMillis);
		Date expiresAt = new Date(currentTimeMillis + expSecond * 1000);
		String jwt = JWT.create().withAudience(generateJWTAudience(expiresAt, answer)).withIssuedAt(currentAt).withExpiresAt(expiresAt).sign(Algorithm.HMAC256(jwtSecretKey));
		
		byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue(), jwtSecretKey.getBytes()).getEncoded();
		SymmetricCrypto des = new SymmetricCrypto(SymmetricAlgorithm.DES, key);
		return des.encryptHex(jwt);
	}
	
	/**
	 * 
	 * 功能描述：生成JWT的Audience属性 <p/>
	 * 生成算法：MD5(expiresAt + jwtSecretKey + answer)
	 * 
	 * @param expiresAt 过期时间
	 * @param answer 图形验证码
	 * @return Audience值
	 * 
	 * @author 刘曙
	 *
	 * @since 2024年4月14日
	 *
	 * @update:[变更日期YYYY-MM-DD][更改人姓名][变更描述]
	 */
	private String generateJWTAudience(final Date expiresAt, final String answer) {
	    final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	    return DigestUtils.md5DigestAsHex((sdf.format(expiresAt) + jwtSecretKey + answer).getBytes());
	}
}
